package com.jeeplus.modules.api.wxpay;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.util.*;

public class WxPayUtils {

	private static final Logger logger = LoggerFactory.getLogger(WxPayUtils.class);
	/**
	 * 交易类型
	 */
	public static final String TRADE_TYPE_JSAPI = "JSAPI";// JSAPI支付（或小程序支付）

	/**
	 * 微信「统一下单」接口地址
	 */
	private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

	/**
	 * 发起微信支付
	 *
	 * @param openid
	 * @param body
	 *            商品描述
	 * @param attach
	 *            附加数据，在查询API和支付通知中原样返回，可作为自定义参数使用（0-商户充值，1-商户订单支付）
	 *            订单号
	 * @return
	 * @throws Exception
	 */
	public static LinkedHashMap<String, Object> wxPay(String openid, String body, String out_trade_no, String price, String attach, String appId, String mchNo, String notifyUrl, String mchSecret) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		LinkedHashMap<String, Object> resultObj = new LinkedHashMap<String, Object>();
		try {
			price = new BigDecimal(price).multiply(new BigDecimal(100)).intValue() + "";
			System.out.println("price:" + price);
			// 生成的随机字符串
			String nonce_str = getUUID();
			// 获取客户端的ip地址
			String spbill_create_ip = IpUtil.getIpAddr(request);
			Map<String, String> packageParams = new HashMap<String, String>();
			packageParams.put("appid", appId);
			packageParams.put("mch_id", mchNo);
			packageParams.put("nonce_str", nonce_str);
			packageParams.put("body", body);
			packageParams.put("out_trade_no", out_trade_no);// 商户订单号
			packageParams.put("total_fee", price);// 支付金额，这边需要转成字符串类型，否则后面的签名会失败
			packageParams.put("spbill_create_ip", spbill_create_ip);
			packageParams.put("notify_url", notifyUrl);// 支付成功后的回调地址
			packageParams.put("trade_type", TRADE_TYPE_JSAPI);// 支付方式
			packageParams.put("openid", openid);
			packageParams.put("attach", attach); // 附加数据，在查询API和支付通知中原样返回，可作为自定义参数使用。
			String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
			// MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
			String mysign = PayUtil.sign(prestr, mchSecret, "utf-8").toUpperCase();
			// 拼接统一下单接口使用的xml数据，要将上一步生成的签名一起拼接进去
			String xml = "<xml>" + "<appid>" + appId + "</appid>" + "<body><![CDATA[" + body + "]]></body>"
					+ "<mch_id>" + mchNo + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<notify_url>"
					+ notifyUrl + "</notify_url>" + "<openid>" + openid + "</openid>" + "<out_trade_no>" + out_trade_no
					+ "</out_trade_no>" + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
					+ "<total_fee>" + price + "</total_fee>" + "<trade_type>" + TRADE_TYPE_JSAPI + "</trade_type>"
					+ "<sign>" + mysign + "</sign>" + "<attach>" + attach + "</attach>" + "</xml>";
			System.out.println("调试模式_统一下单接口 请求XML数据：" + xml);
			// 调用统一下单接口，并接受返回的结果
			String result = PayUtil.httpRequest(UNIFIEDORDER_URL, "POST", xml);
			System.out.println("调试模式_统一下单接口 返回XML数据：" + result);
			// 将解析结果存储在HashMap中
			Map<String, Object> map = PayUtil.doXMLParse(result);
			String return_code = (String) map.get("return_code");// 返回状态码
			if (return_code.equals("SUCCESS") || return_code.equals(return_code)) {
				String prepay_id = (String) map.get("prepay_id");// 返回的预付单信息
				resultObj.put("nonceStr", nonce_str);
				resultObj.put("package", "prepay_id=" + prepay_id);
				Long timeStamp = System.currentTimeMillis() / 1000;
				resultObj.put("timeStamp", timeStamp + "");// 这边要将返回的时间戳转化成字符串，不然小程序端调用wx.requestPayment方法会报签名错误
				// 拼接签名需要的参数
				String stringSignTemp = "appId=" + appId + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id
						+ "&signType=MD5&timeStamp=" + timeStamp;
				// 再次签名，这个签名用于小程序端调用wx.requesetPayment方法
				String paySign = PayUtil.sign(stringSignTemp, mchSecret, "utf-8").toUpperCase();
				resultObj.put("paySign", paySign);
				resultObj.put("appId", appId);
				resultObj.put("signType", "MD5");
				// 返回给小程序端需要的参数
				System.out.println("nonceStr：" + nonce_str);
				System.out.println("package：" + prepay_id);
				System.out.println("timeStamp：" + timeStamp);
				System.out.println("paySign：" + paySign);
				System.out.println("appid：" + appId);
				System.out.println("signType：" + "MD5");
			}
		} catch (Exception e) {
			logger.error("系统异常");
		}
		return resultObj;
	}

	public static String createDocumentForEnterprisesPayment(EnterprisesPayment enterprisesPayment,Integer amount) {
		System.out.println("enterprisesPayment.getAmount():"+amount);
		final StringBuffer result = new StringBuffer();
		result.append("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><xml>");
		result.append("<mch_appid>" + enterprisesPayment.getMch_appid() + "</mch_appid>");
		result.append("<mchid>" + enterprisesPayment.getMchid() + "</mchid>");
		result.append("<nonce_str>" + enterprisesPayment.getNonce_str() + "</nonce_str>");
		result.append("<partner_trade_no>" + enterprisesPayment.getPartner_trade_no() + "</partner_trade_no>");
		result.append("<openid>" + enterprisesPayment.getOpenid() + "</openid>");
		result.append("<check_name>" + enterprisesPayment.getCheck_name() + "</check_name>");
		/* result.append("<re_user_name>" + enterprisesPayment.getRe_user_name() + "</re_user_name>");*/
		result.append("<amount>" +amount+ "</amount>");
		result.append("<desc>" + enterprisesPayment.getDesc() + "</desc>");
		result.append("<spbill_create_ip>" + enterprisesPayment.getSpbill_create_ip() + "</spbill_create_ip>");
		result.append("<sign>" + enterprisesPayment.getSign() + "</sign>");
		result.append("</xml>");
		System.out.println("---------------:"+result.toString());
		return result.toString();
	}

	/**
	 * XML格式字符串转换为Map
	 * 微信支付 解析xml xml转map  获取prepay_id
	 * @param strXML XML字符串
	 * @return XML数据转换后的Map
	 * @throws Exception
	 */
	public static Map<String, String> xmlToMap(String strXML) throws Exception {
		try {
			Map<String, String> data = new HashMap<String, String>();
			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
			InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
			org.w3c.dom.Document doc = documentBuilder.parse(stream);
			doc.getDocumentElement().normalize();
			NodeList nodeList = doc.getDocumentElement().getChildNodes();
			for (int idx = 0; idx < nodeList.getLength(); ++idx) {
				Node node = nodeList.item(idx);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					org.w3c.dom.Element element = (org.w3c.dom.Element) node;
					data.put(element.getNodeName(), element.getTextContent());
				}
			}
			try {
				stream.close();
			} catch (Exception ex) {
				// do nothing
			}
			return data;
		} catch (Exception ex) {
			WxPayUtils.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
			throw ex;
		}

	}

	public static String getSignCode(Map<String, String> map,String keyValue) {
		String result = "";
		try {
			List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(map.entrySet());
			// 对所有传入参数按照字段名的 ASCII 码从小到大排序（字典序）
			Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {

				public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
					return (o1.getKey()).toString().compareTo(o2.getKey());
				}
			});

			// 构造签名键值对的格式
			StringBuilder sb = new StringBuilder();
			for (Map.Entry<String, String> item : infoIds) {
				if (item.getKey() != null || item.getKey() != "") {
					String key = item.getKey();
					String val = item.getValue();
					if (!(val == "" || val == null)) {
						sb.append(key + "=" + val + "&");
					}
				}

			}
			sb.append("key="+keyValue);
			result = sb.toString();

			//进行MD5加密
			result = md5(result).toUpperCase();
		} catch (Exception e) {
			return null;
		}
		return result;
	}

	/**
	 *  md5加密
	 * @param text
	 * @return
	 */
	public static String md5(final String text) {
		//自己写一个md5方法 这里我就不写了  记得大写
		return MD5Utils.MD5(text).toUpperCase();
	}

	/**
	 *  生成提现订单号
	 * @return string
	 */
	public static String getOrderId() {
		int machineId = 1;//最大支持1-9个集群机器部署
		int hashCodeV = UUID.randomUUID().toString().hashCode();
		if(hashCodeV < 0) {//有可能是负数
			hashCodeV = - hashCodeV;
		}
		return machineId+String.format("%015d", hashCodeV);
	}

	/**
	 * @Author: HONGLINCHEN
	 * @Description:请求值转换为xml格式 SortedMap转xml
	 * @param params
	 * @Date: 2017-9-7 17:18
	 */
	private static String SortedMaptoXml(SortedMap<String,String> params) {
		StringBuilder sb = new StringBuilder();
		Set es = params.entrySet();
		Iterator it = es.iterator();
		sb.append("<xml>\n");
		while(it.hasNext()) {
			Map.Entry entry = (Map.Entry)it.next();
			String k = (String)entry.getKey();
			Object v = entry.getValue();
			sb.append("<"+k+">");
			sb.append(v);
			sb.append("</"+k+">\n");
		}
		sb.append("</xml>");
		return sb.toString();
	}

	public static Logger getLogger() {
		Logger logger = LoggerFactory.getLogger("wxpay java sdk");
		return logger;
	}


	/**
	 * 生成32位随机数字
	 */
	public static String genNonceStr() {
		Random random = new Random();
		return getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
	}

	public static String getMessageDigest(byte[] buffer) {
		char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
		try {
			MessageDigest mdTemp = MessageDigest.getInstance("MD5");
			mdTemp.update(buffer);
			byte[] md = mdTemp.digest();
			int j = md.length;
			char[] str = new char[j * 2];
			int k = 0;
			for (byte byte0 : md) {
				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
				str[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(str);
		} catch (Exception e) {
			return null;
		}
	}


	/**
	 * 生成UUID字符串
	 *
	 * @return string UUID
	 */
	static String getUUID() {
		return UUID.randomUUID().toString().replaceAll("-", "");
	}

}
